﻿using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Xml.Serialization;

using ABCat.Shared;
using ABCat.Shared.Properties;

namespace ABCat.Core
{
	public abstract class Config : Saveable
	{
		#region + Fields +

		protected static readonly string LayoutFolderPath;

		private static readonly string ConfigFolderPath;

		#endregion
		#region + Properties +

		[Browsable(false)]
		public abstract string DisplayName		{get;}
		[Browsable(false)]
		[XmlIgnore]
		public bool IsChanged		{get; private set;}
		public override string SaveGroupId
		{
			get { return GetPluginConfigFilePath(GetType()); }
		}

		#endregion
		#region + Ctor +

		static Config()
		{
			ConfigFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ABCat", "Configs");
			Directory.CreateDirectory(ConfigFolderPath);
			LayoutFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ABCat", "Layouts");
			Directory.CreateDirectory(LayoutFolderPath);
		}

		public Config()
		{
		}

		#endregion
		#region + Logic +

		public static bool CheckDirectory([CanBeNull] string path)
		{
			return !string.IsNullOrEmpty(path) && Directory.Exists(path);
		}

		public static bool CheckFile([CanBeNull] string path)
		{
			return !string.IsNullOrEmpty(path) && File.Exists(path);
		}

		[NotNull]
		public static Config CreateDefaultInstance([NotNull] Context context, Type configType)
		{
			Config result = null;
			var contextCtor = configType.GetConstructor(new[] { typeof(Context) });
			if (contextCtor != null && contextCtor.IsPublic) result = (Config)Activator.CreateInstance(configType, context);
			else
			{
				var defaultCtor = configType.GetConstructor(new Type[0]);
				if (defaultCtor != null && defaultCtor.IsPublic) result = (Config)Activator.CreateInstance(configType);
				else throw new Exception("Класс конфигурации '{0}' '{1}' не имеет поддерживаемых конструкторов".F(configType.Assembly.FullName, configType.FullName));
			}

			return result;
		}

		[NotNull]
		public static T Load<T>([NotNull] Context context)
			where T : Config
		{
			return (T)Load(context, typeof(T));
		}

		[NotNull]
		public static Config Load([NotNull] Context context, [NotNull] Type configType)
		{
			Config result = null;

			if (!TryLoad(configType, out result))
			{
				result = CreateDefaultInstance(context, configType);
			}

			result.IsChanged = false;

			return result;
		}

		public static void SaveLayout([NotNull] string layoutName, [NotNull] byte[] layout)
		{
			File.WriteAllBytes(GetLayoutFileName(layoutName), layout);
		}

		[UsedImplicitly]
		public static bool TryLoad([NotNull] Type configType, out Config config)
		{
			string file = GetPluginConfigFilePath(configType);
			if (File.Exists(file))
			{
				using (var fs = File.OpenRead(file))
				{
					var xs = new XmlSerializer(configType);
					config = (Config)xs.Deserialize(fs);
					return true;
				}
			}

			config = null;
			return false;
		}

		public static bool TryLoadLayout([NotNull] string layoutName, [CanBeNull] out byte[] layout)
		{
			layout = null;
			string layoutFileName = GetLayoutFileName(layoutName);
			if (File.Exists(layoutFileName))
			{
				layout = File.ReadAllBytes(layoutFileName);
				return true;
			}

			return false;
		}

		public abstract bool Check(bool correct);

		public override void Save()
		{
			this.SerializeToXmlFile(GetPluginConfigFilePath(GetType()));
			IsChanged = false;
		}

		public override void SaveGroup(System.Collections.Generic.IEnumerable<Saveable> group)
		{
			foreach (var saveable in group)
			{
				saveable.Save();
			}
		}

		public override string ToString()
		{
			return DisplayName;
		}

		protected override void OnPropertyChanged(string propertyName = null)
		{
			IsChanged = true;
			base.OnPropertyChanged(propertyName);
		}

		[NotNull]
		private static string GetLayoutFileName([NotNull] string layoutName)
		{
			var invalid = new string(Path.GetInvalidFileNameChars());
			if (invalid.Any(layoutName.Contains)) throw new Exception("Имя лайяута '{0}' содержит недопустимые символы".F(layoutName));
			return Path.Combine(LayoutFolderPath, layoutName);
		}

		private static string GetPluginConfigFilePath(Type configType)
		{
			return Path.Combine(ConfigFolderPath, "{0}.xml".F(configType.Name));
		}

		#endregion
	}
}